/*
    event.c.tst - Unit tests for Events

    Copyright (c) All Rights Reserved. See details at the end of the file.
 */

/********************************** Includes **********************************/

#include    "testme.h"
#include    "mpr.h"

/*********************************** Locals ***********************************/

typedef struct Outside {
    int     *result;
    MprCond *cond;
} Outside;

static int waitNap = TM_SHORT_NAP;
static int stressDuration = (5 * 60 * 1000);
static void insideEvent(void *data, MprEvent *event);
static void outsideThread(void *result, MprThread *tp);

/************************************ Code ************************************/
/*
    Event callback used by a few unit tests. This signals completion of the unit test.
 */
static void eventCallback(void *data, MprEvent *event)
{
    if (data) {
        mprSignalCond(data);
    }
}


static void createEvent()
{
    MprEvent    *event;
    MprCond     *cond;

    cond = mprCreateCond();
    event = mprCreateEvent(NULL, "testCreateEvent", 0, eventCallback, cond, MPR_EVENT_QUICK);
    ttrue(event != 0);
    ttrue(mprWaitForCond(cond, waitNap) == 0);

    /*
        Run event callback on a separate thread
     */
    event = mprCreateEvent(NULL, "testCreateEvent", 0, eventCallback, cond, 0);
    ttrue(event != 0);
    ttrue(mprWaitForCond(cond, waitNap) == 0);
}


static void cancelEvent()
{
    MprEvent    *event;

    event = mprCreateEvent(NULL, "testCancelEvent", 20000, eventCallback, 0, MPR_EVENT_QUICK);
    ttrue(event != 0);
    mprRemoveEvent(event);
}


static void reschedEvent()
{
    MprEvent    *event;
    MprCond     *cond;
    MprTicks    mark;

    cond = mprCreateCond();
    tfalse(cond->triggered);

    event = mprCreateEvent(NULL, "testReschedEvent", 50000000, eventCallback, cond, MPR_EVENT_QUICK);
    ttrue(event != 0);
    mprRescheduleEvent(event, 20);

    for (mark = mprGetTicks(); !cond->triggered && mprGetElapsedTicks(mark) < waitNap; ) {
        mprWaitForEvent(NULL, 10, -1);
    }
    ttrue(mprWaitForCond(cond, 0) == 0);
}


/*
    Test a single outside event
 */
static void outsideEvent()
{
    MprThread   *tp;
    int         rc, result;

    /*
        Simulate an outside / foreign thread by yielding
     */
    result = 0;
    tp = mprCreateThread("outside", outsideThread, &result, 0);
    ttrue(tp != 0);
    rc = mprStartThread(tp);
    ttrue(rc == 0);
    mprYield(MPR_YIELD_STICKY);

    /*
        Wait for the thread to complete
     */
    ttrue(mprWaitForCond(tp->cond, waitNap) == 0);
    mprResetYield();
    ttrue(result == 42);
}


/*
    Outside (foreign) thread for outsideEvent. Created as an mpr thread, but then yielded to simulate a foreign thread
 */
static void outsideThread(void *result, MprThread *tp)
{
    Outside     outside;
    MprEvent    *event;

    outside.result = result;
    outside.cond = mprCreateCond();
    mprAddRoot(outside.cond);

    /*
        Pretend this is a non-mpr thread by yielding here
     */
    mprYield(MPR_YIELD_STICKY);

    event = mprCreateEvent(NULL, "insideEvent", 0, insideEvent, &outside, 0);
    ttrue(event);

    ttrue(mprWaitForCond(outside.cond, waitNap) == 0);
    mprRemoveRoot(outside.cond);

    mprSignalCond(tp->cond);
}


/*
    insideEvent for outsideStress and outsideEvent
    Event runs inside the MPR on an MPR thread
 */
static void insideEvent(void *data, MprEvent *event)
{
    Outside     *outside;

    outside = (Outside*) data;
    if (!outside) {
        ttrue(outside);
    }
    if (outside->result) {
        *outside->result = 42;
    }
    if (outside->cond) {
        mprSignalCond(outside->cond);
    }
    /*
        Integrity tests
     */
    slen(event->name);
    slen(event->dispatcher->name);
}


static int memActive = 1;
static int eventActive = 1;

/*
    This will allocate memory and sleep without yielding.
    This will create the conditions where a GC is required, but cannot yet run
 */
static void memStressThread()
{
    MprTicks    now;
    void        *mp;
    int         i, depth;

    depth = tdepth();
    now = mprGetTicks();

    while (mprGetElapsedTicks(now) < stressDuration) {
        for (i = 0; i < 1000; i++) {
            mp = mprAlloc(64);
            if (mp == 0) {
                ttrue(mp != 0);
            }
        }
        mprYield(0);
        mprNap(rand() % 5);
    }
    memActive = 0;
}


static void outsideStressThread(void *unused, MprThread *tp)
{
    static Outside  outside;
    MprTicks        now;
    MprEvent        *event;
    int             count, rc;

    /*
        Pretend this is a non-mpr thread by yielding here
     */
    mprYield(MPR_YIELD_STICKY);
    outside.result = NULL;
    outside.cond = NULL;

    now = mprGetTicks();
    for (count = 0; mprGetElapsedTicks(now) < stressDuration; count++) {
        /*
            Test blocking events
         */
        outside.cond = tp->cond;
        rc = mprCreateEventOutside(NULL, "insideEvent", insideEvent, &outside, 0);
        if (rc) {
            ttrue(rc == 0);
        }
        if (mprWaitForCond(tp->cond, waitNap) != 0) {
            ttrue(0);
        }
        mprNap(rand() % 10);

        /*
            Non-blocking
         */
        outside.cond = 0;
        event = mprCreateEvent(NULL, "insideEvent", 0, insideEvent, &outside, 0);
        if (!event) {
            ttrue(event);
        }
        mprNap(rand() % 2);
    }
    eventActive = 0;
}


static void outsideStress()
{
    MprThread   *tp;
    int         rc;

    /*
        Stress memory now to create a condition where GC is required.
     */
    tp = mprCreateThread("memStress", memStressThread, NULL, 0);
    ttrue(tp != 0);
    rc = mprStartThread(tp);
    ttrue(rc == 0);

    tp = mprCreateThread("outsideStress", outsideStressThread, NULL, 0);
    ttrue(tp != 0);
    rc = mprStartThread(tp);
    ttrue(rc == 0);

    mprYield(MPR_YIELD_STICKY);
    while (eventActive || memActive) {
        mprNap(10);
    }
    mprResetYield();
}


int main(int argc, char **argv)
{
    mprCreate(argc, argv, 0);

    /* 
        Long nap timeout when debugging 
    */
    if (argc >= 1 && smatch(argv[1], "-D")) {
        mprSetDebugMode(1);
        waitNap = stressDuration;
    }
    createEvent();
    cancelEvent();
    reschedEvent();
    outsideEvent();
    if (tdepth() >= 5) {
        outsideStress();
    }
    return 0;
}

/*
    @copy   default

    Copyright (c) Embedthis Software. All Rights Reserved.

    This software is distributed under commercial and open source licenses.
    You may use the Embedthis Open Source license or you may acquire a 
    commercial license from Embedthis Software. You agree to be fully bound
    by the terms of either license. Consult the LICENSE.md distributed with
    this software for full details and other copyrights.

    Local variables:
    tab-width: 4
    c-basic-offset: 4
    End:
    vim: sw=4 ts=4 expandtab

    @end
 */
